#coding=utf-8

from __future__ import with_statement

from former_project.common_logic.db.schema import DatabaseSchema, TableSchema, Field
from former_project.common_logic.db import ConnectionPool

class Operator(object):
    Select = 0x1
    Insert = 0x2
    Update = 0x4
    Delete = 0x8
    
class ModelStatus(object):
    Nothing = 0
    Inited = 0x1
    Changed = 0x2
    Deleted = 0x3
    
class ModelMeta(type):
    def __new__(cls, name, bases, attrs):
        super_new = super(ModelMeta, cls).__new__
        parents = [b for b in bases if isinstance(b, ModelMeta)]
        if not parents:
            return super_new(cls, name, bases, attrs)
        #create new class
        module = attrs.pop('__module__')
        new_class = super_new(cls, name, bases, {'__module__': module})
        #check table schema
        table_schema = None
        db_table = attrs.pop("db_table", None)
        if db_table is None and 'Meta' in attrs: # 兼容django model的Meta
            db_table = getattr(attrs['Meta'], "db_table")
        table_tuple = None
        if hasattr(db_table, "__iter__"):
            table_tuple = db_table
            db_table = table_tuple[0]
        if not db_table:
            segment = []
            begin = 0
            for i in range(len(name)):
                if name[i].isupper() and i != 0:
                    segment.append(name[begin:i].lower())
                    begin = i
            segment.append(name[begin:].lower())
            db_table = "_".join(segment)
        if not isinstance(db_table, basestring):
            raise Exception("db_table must be string")
        if not DatabaseSchema.tables.has_key(db_table):
            table_schema = TableSchema(db_table, new_class)
        #add db attributes
        attr_keys = attrs.keys()
        for k in attr_keys:
            v = attrs[k]
            if isinstance(v, Field):
                attrs.pop(k)
                setattr(new_class, k, None)
                if table_schema:
                    if not v.name:
                        v.name = k
                    table_schema.fields[k] = v
                    if v.primary_key:
                        table_schema.primary_key.append(v)

        if table_schema and not table_schema.primary_key:
            pk = Field("unsigned", null = False, primary_key = True)
            pk.name = "id"
            table_schema.fields["id"] = pk
            table_schema.primary_key.append(pk)
            setattr(new_class, "id", None)
        if table_schema and not DatabaseSchema.tables.has_key(db_table):
            if table_tuple:
                for t in table_tuple:
                    DatabaseSchema.tables[t] = table_schema
            else:
                DatabaseSchema.tables[db_table] = table_schema
        setattr(new_class, "table_schema", DatabaseSchema.tables[db_table])
        #add rest attributes
        for k, v in attrs.items():
            setattr(new_class, k, v)
        return new_class

class Model(object):
    __metaclass__ = ModelMeta
    def __init__(self, **kwargs):
        self._enable_track_change(False)
        self._field_status = {}
        for k, v in kwargs.items():
            if self.table_schema.fields.has_key(k):
                setattr(self, k, v)
                self._track_change(k, ModelStatus.Inited)
            else:
                raise AttributeError("'%r' has no attribute '%s'" % (self, k))
        self._enable_track_change(True)
    
    @classmethod
    def get_schema(cls):
        """get table schema"""
        return cls.table_schema
    
    @classmethod 
    def delete_objects(cls, where, params):
        """delete objects through where, return effect rows count"""
        with ConnectionPool.cursor() as cursor:
            return cursor.delete(cls.table_schema.table_name, where, params)
    
    @classmethod
    def fetch_objects(cls, where=None, params=(), columns=None, 
                      source_result=False):
        """fetch model objects
        
        if source_result is True, return the datarows
        """
        with ConnectionPool.cursor() as cursor:
            rs = cursor.fetch_objects(cls, where, columns=columns, 
                params=params, source_result=source_result)
        return rs
    
    @property
    def dirty_fields(self):
        """get field names which are changed"""
        return [k for k, status in self._field_status.iteritems() 
                if status in (ModelStatus.Inited, ModelStatus.Changed)]
    
    @classmethod
    def count(cls, where=None, params=()):
        if not where:
            where = ""
        sql = "select count(*) from %s %s" % \
            (cls.table_schema.table_name, where)
        with ConnectionPool.cursor() as cursor:
            r = cursor.fetchone(sql, params)
        if r:
            return r[0]
        else:
            return 0
        
    @classmethod
    def from_dict(cls, d):
        """根据字典类型生成类实例对象"""
        if not isinstance(d, dict):
            raise TypeError('%r must be `dict`' % d)
        return cls(**d)
        
    def get_primary_key(self, with_value=True):
        """get primary key values"""
        keys = []
        values = []
        for pk in self.table_schema.primary_key:
            keys.append(pk.name)
            v = getattr(self, pk.name)
            if with_value and v:
                values.append(v)
        if with_value:
            return tuple(keys), tuple(values)
        else:
            return tuple(keys)
    
    def insert(self):
        """insert current model object into database"""
        with ConnectionPool.cursor() as cursor:
            last_id = cursor.insert(self)
        self._field_status.clear()
        return last_id
    
    def update(self, where=None, params=()):
        """update current model object into database
        
        If where is None, instance's primary key must be set.
        """
        with ConnectionPool.cursor() as cursor:
            effected = cursor.update(self, where, params=params)
        self._field_status.clear()
        return effected
    
    def save(self):
        """save current model object into database"""
        with ConnectionPool.cursor() as cursor:
            return_value = cursor.save_object(self)
        self._field_status.clear()
        return return_value
    
    def delete(self):
        """delete model from database"""
        with ConnectionPool.cursor() as cursor:
            cursor.delete_by_pk(self, self.get_primary_key()[1])
    
    def to_dict(self):
        """获取类实例对象数据的dict表示"""
        d = {}
        for k in self.table_schema.fields.keys():
            d[k] = getattr(self, k)
        return d
    
    def get(self, attrname):
        """实现dict的get接口"""
        return getattr(self, attrname)
    
    def _enable_track_change(self, value):
        object.__setattr__(self, "_track_change_enabled", value)
    
    def _track_change(self, name, status):
        if self.table_schema.fields.has_key(name):
            s = self._field_status.get(name, ModelStatus.Nothing)
            self._field_status[name] = s | status
    
    def __setattr__(self, name, value):
        if self._track_change_enabled:
            self._track_change(name, ModelStatus.Changed)
        return object.__setattr__(self, name, value)
